package com.spsy;

import com.spsy.pojo.Department;
import com.spsy.pojo.Employee;
import com.spsy.pojo.Student;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Author: Sokai
 * @Date: 2024/10/23 08:39
 * @Description:
 **/
@SpringBootApplication
public class BookApplication {
    public static void main(String[] args) {

//        //TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text
//        // to see how IntelliJ IDEA suggests fixing it.
//        System.out.printf("Hello and welcome!");
//
//        for (int i = 1; i <= 5; i++) {
//            //TIP Press <shortcut actionId="Debug"/> to start debugging your code. We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
//            // for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>.
//            System.out.println("i = " + i);
//        }

                // 使用Stream.forEach()迭代
//        Stream<String> stream = Stream.of("I", "love", "you", "too");
//        stream.forEach(str -> System.out.println(str));


                // 保留长度等于3的字符串
//        Stream<String> stream1 = Stream.of("I", "love", "you", "too");
//        stream1.filter(str -> str.length()==3)
//                .forEach(str -> System.out.println(str));


                // 去重
//        Stream<String> stream= Stream.of("I", "love", "you", "too", "too");
//        stream.distinct()
//                .forEach(str -> System.out.println(str));

                // 排序， 按照字符串长度排序
//        Stream<String> stream= Stream.of("I", "love", "you", "too");
//        stream.sorted((str1, str2) -> str1.length()-str2.length())
//                .forEach(str -> System.out.println(str));


                // 把每一个字符串对象字母转成大写
//        Stream<String> stream = Stream.of("I", "love", "you", "too");
//        stream.map(str -> str.toUpperCase())
//                .forEach(str -> System.out.println(str));


                // 处理两个列表操作
//        Stream<List<Integer>> stream = Stream.of(Arrays.asList(1,2), Arrays.asList(3, 4, 5));
//        stream.flatMap(list -> list.stream())
//                .forEach(i -> System.out.println(i));


                // 需求：从一组单词中找出最长的单词。这里“大”的含义就是“长”。
//        Stream<String> stream = Stream.of("I", "love", "you", "too");
////        Optional<String> longest = stream.reduce((s1, s2) -> s1.length()>=s2.length() ? s1 : s2); // method-1
//        Optional<String> longest = stream.max((s1, s2) -> s1.length()-s2.length());                 // method-2
//        System.out.println(longest.get());


                // 需求：求出一组单词的长度之和。这是个“求和”操作，操作对象输入类型是String，而结果类型是Integer。
//        Stream<String> stream = Stream.of("I", "love", "you", "too");
////        Integer lengthSum = stream.reduce(0,  // 初始值　// (1)
////                (sum, str) -> sum+str.length(),       // 累加器 // (2)
////                (a, b) -> a+b);                       // 部分和拼接器，并行执行时才会用到 // (3)
//         int lengthSum = stream.mapToInt(str -> str.length()).sum();
//        System.out.println(lengthSum);


                /**
                 * 重要API: collect()
                 * collect()是Stream接口方法中最灵活的一个，学会它才算真正入门Java函数式编程。
                 */
                // 将Stream转换成容器或Map/list/set
                Stream<String> stream = Stream.of("I", "love", "you", "too");
                List<String> list = stream.collect(Collectors.toList()); // toList
//        Set<String> set = stream.collect(Collectors.toSet());    // toSet

                // 通过stream,转map为list, 其它转换以次类推
                Set<String> collect = list.stream().collect(Collectors.toSet());
                System.out.println(collect.size());


                /**
                 * 知识点：
                 * 接口的静态方法和默认方法
                 * Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String::length));
                 *
                 * Function是一个接口，那么Function.identity()是什么意思呢？这要从两方面解释：
                 * Java 8允许在接口中加入具体方法。接口中的具体方法有两种，default方法和static方法，identity()就是Function接口的一个静态方法。
                 * Function.identity()返回一个输出跟输入一样的Lambda表达式对象，等价于形如t -> t形式的Lambda表达式。
                 */


                /**
                 * 知识点：
                 * 方法引用
                 * 诸如String::length的语法形式叫做方法引用（method references），这种语法用来替代某些特定形式Lambda表达式。
                 * 如果Lambda表达式的全部内容就是调用一个已有的方法，那么可以用方法引用来替代Lambda表达式。方法引用可以细分为四类：
                 *
                 * 【方法引用类别】	    【举例】
                 * 引用静态方法	        Integer::sum
                 * 引用某个对象的方法	    list::add
                 * 引用某个类的方法	    String::length
                 * 引用构造方法	        HashMap::new
                 *
                 */


                /**
                 * 知识点：
                 * 收集器 **** 重点
                 * 集器（Collector）是为Stream.collect()方法量身打造的工具接口（类）。考虑一下将一个Stream转换成一个容器（或者Map）需要做哪些工作？
                 * 我们至少需要两样东西：
                 *
                 * 1、目标容器是什么？是ArrayList还是HashSet，或者是个TreeMap。
                 * 2、新元素如何添加到容器中？是List.add()还是Map.put()。
                 * 3、多个部分结果如何合并成一个。
                 *
                 * 格式说明：
                 * collect()方法定义为：<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)，
                 * 三个参数依次对应上述三条分析。
                 * 不过每次调用collect()都要传入这三个参数太麻烦，收集器Collector就是对这三个参数的简单封装,
                 * 所以collect()的另一定义为<R,A> R collect(Collector<? super T,A,R> collector)。
                 * Collectors工具类可通过静态方法生成各种常用的Collector。
                 *
                 * 通常情况下我们不需要手动指定collect()的三个参数，而是调用collect(Collector<? super T,A,R> collector)方法，
                 * 并且参数中的Collector对象大都是直接通过Collectors工具类获得。实际上传入的收集器的行为决定了collect()的行为。
                 *
                 */

                // (1)
                // 应用：使用collect()生成Collection
                // 前面已经提到通过collect()方法将Stream转换成容器的方法，这里再汇总一下。将Stream转换成List或Set是比较常见的操作，
                // 所以Collectors工具已经为我们提供了对应的收集器，通过如下代码即可完成：
                // 将Stream转换成List或Set
//        Stream<String> stream = Stream.of("I", "love", "you", "too");
//        List<String> list = stream.collect(Collectors.toList()); // (1)
//        Set<String> set = stream.collect(Collectors.toSet()); // (2)

                // (2)
                // 上述代码能够满足大部分需求，但由于返回结果是接口类型，我们并不知道类库实际选择的容器类型是什么，
                // 有时候我们可能会想要人为指定容器的实际类型，这个需求可通过Collectors.toCollection(Supplier<C> collectionFactory)方法完成。
                // 使用toCollection()指定规约容器的类型
//        ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));// (3)
//        HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));// (4)

                /**
                 * 使用collect()生成Map
                 *
                 * 前面已经说过Stream背后依赖于某种数据源，数据源可以是数组、容器等，但不能是Map。反过来从Stream生成Map是可以的，但我们要想清楚Map的key和value分别代表什么，根本原因是我们要想清楚要干什么。通常在三种情况下collect()的结果会是Map：
                 *
                 * 使用Collectors.toMap()生成的收集器，用户需要指定如何生成Map的key和value。
                 * 使用Collectors.partitioningBy()生成的收集器，对元素进行二分区操作时用到。
                 * 使用Collectors.groupingBy()生成的收集器，对元素做group操作时用到。
                 *
                 */

                List<Student> students = new ArrayList<>();
                Student student1 = new Student(18);
                Student student2 = new Student(20);
                Student student3 = new Student(25);
                Student student4 = new Student(18);
                Student student5 = new Student(20);
                Student student6 = new Student(25);
                students.add(student1);
                students.add(student2);
                students.add(student3);
                students.add(student4);
                students.add(student5);
                students.add(student6);

                // 情况1：使用toMap()生成的收集器，这种情况是最直接的，前面例子中已提到，这是和Collectors.toCollection()并列的方法。
                // 如下代码展示将学生列表转换成由<学生，GPA>组成的Map。
//        Map<knowleage.Student, Double> studentToGPA =
//                students.stream().collect(Collectors.toMap(Function.identity(),// 如何生成key
//                        student -> computeGPA(student)));// 如何生成value

                // 情况2：使用partitioningBy()生成的收集器，这种情况适用于将Stream中的元素依据某个二值逻辑（满足条件，或不满足）分成互补相交的两部分，
                // 比如男女性别、成绩及格与否等。下列代码展示将学生分成成绩及格或不及格的两部分。
                // Partition students into passing and failing
                Map<Boolean, List<Student>> passingFailing = students.stream()
                        .collect(Collectors.partitioningBy(s -> s.getAge() >= 20));
                System.out.println(passingFailing.toString());
                // 运行结果：{false=[knowleage.Student@4f3f5b24, knowleage.Student@15aeb7ab], true=[knowleage.Student@7b23ec81, knowleage.Student@6acbcfc0, knowleage.Student@5f184fc6, knowleage.Student@3feba861]}

                // 情况3：使用groupingBy()生成的收集器，这是比较灵活的一种情况。跟SQL中的group by语句类似，这里的groupingBy()也是按照某个属
                // 性对数据进行分组，属性相同的元素会被对应到Map的同一个key上。下列代码展示将员工按照部门进行分组：

                List<Employee> employees = new ArrayList<>();

                // Group employees by department
                Map<Department, List<Employee>> byDept = employees.stream()
                        .collect(Collectors.groupingBy(Employee::getDepartment));

                // Group student by age
                Map<Integer, List<Student>> studentMap = students.stream()
                        .collect(Collectors.groupingBy(Student::getAge));
                System.out.println(studentMap);
                // 运行结果：{18=[knowleage.Student@4f3f5b24, knowleage.Student@15aeb7ab], 20=[knowleage.Student@7b23ec81, knowleage.Student@5f184fc6], 25=[knowleage.Student@6acbcfc0, knowleage.Student@3feba861]}

                // 以上只是分组的最基本用法，有些时候仅仅分组是不够的。在SQL中使用group by是为了协助其他查询，
                // 比如
                // 1. 先将员工按照部门分组，
                // 2. 然后统计每个部门员工的人数。Java类库设计者也考虑到了这种情况，增强版的groupingBy()能够满足这种需求。
                //    增强版的groupingBy()允许我们对元素分组之后再执行某种运算，比如求和、计数、平均值、类型转换等。
                //    这种先将元素分组的收集器叫做上游收集器，之后执行其他运算的收集器叫做下游收集器(downstream Collector)。
                // 使用下游收集器统计每个部门的人数
                Map<Department, Long> totalByDept = employees.stream()
                        .collect(Collectors.groupingBy(Employee::getDepartment,
                                Collectors.counting()));// 下游收集器

                // 上面代码的逻辑是不是越看越像SQL？高度非结构化。还有更狠的，下游收集器还可以包含更下游的收集器，这绝不是为了炫技而增加的把戏，
                // 而是实际场景需要。考虑将员工按照部门分组的场景，如果我们想得到每个员工的名字（字符串），
                // 而不是一个个Employee对象，可通过如下方式做到：
                // 按照部门对员工分组，并只保留员工的名字
                Map<Department, List<String>> departmentListMap = employees.stream()
                        .collect(Collectors.groupingBy(Employee::getDepartment,
                                Collectors.mapping(Employee::getName,  // 下游收集器
                                        Collectors.toList())));        // 更下游的收集器


                /**
                 * 使用collect()做字符串join
                 * 这个肯定是大家喜闻乐见的功能，字符串拼接时使用Collectors.joining()生成的收集器，从此告别for循环。
                 * Collectors.joining()方法有三种重写形式，分别对应三种不同的拼接方式。无需多言，代码过目难忘。
                 *
                 */
                // 使用Collectors.joining()拼接字符串
                Stream<String> stream1 = Stream.of("I", "love", "you");
                //String joined = stream.collect(Collectors.joining());// "Iloveyou"
                //String joined = stream.collect(Collectors.joining(","));// "I,love,you"
                String joined = stream1.collect(Collectors.joining(",", "{", "}"));// "{I,love,you}"









    }
    private static Double computeGPA(Student student) {
        return new Double(0.00);
    }
}
